using System;
using System.Linq.Expressions;
using System.Reflection;

namespace Magiq.Support
{
    public static class ExpressionExtensions
    {
        /// <summary>
        ///   Retrieves the property represented by the specified expression
        /// </summary>
        /// <param name = "expression">The expression.</param>
        /// <returns></returns>
        public static PropertyInfo ToPropertyInfo(this LambdaExpression expression)
        {
            var memberExpression = ToMemberExpression(expression);
            return (PropertyInfo) memberExpression.Member;
        }

        public static MethodInfo ToMethodInfo(this LambdaExpression expression)
        {
            var memberExpression = MethodCallExpression(expression);
            return memberExpression.Method;
        }

        public static MemberExpression ToMemberExpression(this LambdaExpression expression)
        {
            return expression.Body.ToMemberExpression();
        }

        public static MethodCallExpression MethodCallExpression(this Expression expression)
        {
            MethodCallExpression methodCallExpression = null;
            switch (expression.NodeType)
            {
                case ExpressionType.Lambda:
                    {
                        var lambda = ((LambdaExpression) expression);
                        methodCallExpression = lambda.Body as MethodCallExpression;
                    }
                    break;
            }

            if (methodCallExpression == null) throw new ArgumentException("Not a method call", "expression");
            return methodCallExpression;
        }

        public static ParameterExpression GetParameter(this Expression expression)
        {
            if (expression.Is<ParameterExpression>())
                return (ParameterExpression) expression;

            if (expression.Is<LambdaExpression>())
                return GetParameter(((LambdaExpression) expression).Body);
            if (expression.Is<MemberExpression>())
                return ((MemberExpression) expression).Expression.GetParameter();
            if (expression.Is<UnaryExpression>())
                return ((UnaryExpression) expression).Operand.GetParameter();
            if (expression.Is<MethodCallExpression>())
                return ((MethodCallExpression) expression).Object.GetParameter();
            if (expression.Is<BinaryExpression>())
            {
                var binary = (BinaryExpression) expression;
                return binary.Left.GetParameter() ?? binary.Right.GetParameter();
            }
            return null;
        }

        public static LambdaExpression PreviousExpressionChain(this LambdaExpression self)
        {
            return self.PreviousExpressionChain(false);
        }

        public static LambdaExpression PreviousExpressionChain(this Expression self, bool includeRoot)
        {
            Expression e = null;
            if (self is LambdaExpression)
                return ((LambdaExpression) self).Body.PreviousExpressionChain(includeRoot);

            if (self is UnaryExpression)
                return ((UnaryExpression) self).Operand.PreviousExpressionChain(includeRoot);

            if (self is MemberExpression)
                e = ((MemberExpression) self).Expression;

            if (e == null || (!includeRoot && e is ParameterExpression))
                return null;

            return Expression.Lambda(e, self.GetParameter());
        }


        public static LambdaExpression LastExpressionChain(this Expression self)
        {
            Expression e = null;
            if (self is LambdaExpression)
                return ((LambdaExpression) self).Body.LastExpressionChain();

            if (self is UnaryExpression)
                return ((UnaryExpression) self).Operand.LastExpressionChain();
            if (self is MemberExpression)
                e = ((MemberExpression) self).Expression;

            if (e == null)
                return null;

            return Expression.Lambda(e, self.GetParameter());
        }


        public static MemberExpression ToMemberExpression(this Expression self)
        {
            if (self is LambdaExpression)
                return ((LambdaExpression) self).Body.ToMemberExpression();

            if (self is UnaryExpression)
                return ((UnaryExpression) self).Operand.ToMemberExpression();

            if (self is MemberExpression)
                return (MemberExpression) self;

            return null;
        }

        private static Expression ConcreteExpression(this Expression self)
        {
            if (self is LambdaExpression)
                return ((LambdaExpression) self).Body.ConcreteExpression();

            if (self is UnaryExpression)
                return (((UnaryExpression) self).Operand.ConcreteExpression());

            if (self is MemberExpression)
                return ((MemberExpression) self).Expression.ConcreteExpression();

            return self;
        }


        public static bool IsConstant(this Expression self)
        {
            return self.ConcreteExpression() is ConstantExpression;
        }

        public static TExpression ChangeParameter<TExpression>(this TExpression self, ParameterExpression parameter)
            where TExpression : Expression
        {
            return self.Accept(new ExpressionParameterChanger(parameter));
        }

        public static TExpression Accept<TExpression>(this TExpression self, ExpressionVisitor visitor)
            where TExpression : Expression
        {
            return visitor.Visit(self);
        }
    }
}